package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.bean.CartInfo;
import com.atguigu.gmall.common.consts.GmallConsts;
import com.atguigu.gmall.common.exception.GmallUserException;
import com.atguigu.gmall.order.feign.*;
import com.atguigu.gmall.order.interceptor.LoginInterceptor;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.vo.OrderConfirmItemVO;
import com.atguigu.gmall.order.vo.OrderConfirmVO;
import com.atguigu.gmall.order.vo.OrderSubmitVO;
import com.atguigu.gmall.pms.entity.SkuAttrValueEntity;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.ums.entity.UserAddressEntity;
import com.atguigu.gmall.ums.entity.UserEntity;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import com.atguigu.gmall.wms.vo.SkuLockVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    GmallUmsClient umsClient;
    @Resource
    GmallCartClient cartClient;
    @Resource
    GmallPmsClient pmsClient;
    @Resource
    GmallOmsClient omsClient;
    @Resource
    GmallWmsClient wmsClient;
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    RabbitTemplate rabbitTemplate;
    @Override
    public OrderConfirmVO queryOrderConfirmVO() {
        OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
        Long userId = LoginInterceptor.getUserId();
        //1、用户账户积分
        UserEntity userEntity = umsClient.queryUserById(userId).getData();
        if(userEntity==null){
            throw new GmallUserException("用户异常",1022);
        }
        orderConfirmVO.setBounds(userEntity.getIntegration());
        //2、根据用户id查询地址列表：  ums提供远程访问接口 根据userId查询地址列表
        List<UserAddressEntity> userAddressEntities = umsClient.queryUserAddress(userId).getData();
        orderConfirmVO.setAddresses(userAddressEntities);
        //3、生成token：防止表单重复提交(访问页面时生成token，页面提交请求时携带token后端验证是否重复提交)
        // 后端token验证： 生成token时缓存到服务器中，页面提交请求获取服务器对应的token比较 ，如果一致可以访问并删除缓存的token，如果不一致拒绝请求
        String orderToken = UUID.randomUUID().toString().replace("-", "");
        stringRedisTemplate.opsForValue().set(GmallConsts.ORDER_TOKEN_PREFIX+orderToken ,"1",
                2, TimeUnit.HOURS);
        orderConfirmVO.setOrderToken(orderToken);
        //4、查询userId用户购物车中选中的购物项： 没个购物项转为一个商品清单中的一个商品
        List<CartInfo> cartInfos = cartClient.queryCheckedCartInfos(userId).getData();
        if(CollectionUtils.isEmpty(cartInfos)){
            throw new GmallUserException("未选中要购买的商品",1020);
        }
        List<OrderConfirmItemVO> orderConfirmItemVOS = cartInfos.stream().map(cartInfo -> {
            OrderConfirmItemVO itemVO = new OrderConfirmItemVO();
            String skuId = cartInfo.getSkuId();

            itemVO.setSkuId(skuId);
            itemVO.setCount(cartInfo.getCount());
            //根据skuId查询pms sku数据
            SkuEntity skuEntity = pmsClient.querySkuById(Long.parseLong(skuId)).getData();
            if(skuEntity==null){
                throw new GmallUserException("商品信息错误",1021);
            }
            itemVO.setTitle(skuEntity.getTitle());
            itemVO.setPrice(skuEntity.getPrice());
            itemVO.setDefaultImage(skuEntity.getDefaultImage());
            //库存：
            List<WareSkuEntity> wareSkuEntities = wmsClient.listBySkuId(Long.parseLong(skuId)).getData();
            if(!CollectionUtils.isEmpty(wareSkuEntities)){
                itemVO.setStore(wareSkuEntities.stream()
                        // 如果 sku任意一个仓库中的库存数量大于等于购买商品的数量 认为有货
                        .anyMatch(wareSkuEntity ->
                                wareSkuEntity.getStock()-wareSkuEntity.getStockLocked()>=cartInfo.getCount()));
            }
            //销售属性
            List<SkuAttrValueEntity> skuAttrValueEntities = pmsClient.searchAttrValues(Long.parseLong(skuId)).getData();
            itemVO.setSaleAttrs(skuAttrValueEntities);
            return itemVO;
        }).collect(Collectors.toList());

        orderConfirmVO.setOrderItems(orderConfirmItemVOS);

        return orderConfirmVO;
    }

    @Override
    public String createOrder(OrderSubmitVO submitVO) {
        //1、防止重复提交
        String orderToken = submitVO.getOrderToken();
        // 如果orderToken在redis中存在删除后 可以继续执行，如果不存在表示订单重复提交/token是伪造的
        // 多个redis操作 如果不希望被插队保证原子性可以使用LUA脚本
        String script = "if(redis.call('exists' , KEYS[1])==1)" +
                " then" +
                "  redis.call('del' , KEYS[1])" +
                "  return 1" +
                " else" +
                "  return 0 end";
        Boolean flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                Arrays.asList(GmallConsts.ORDER_TOKEN_PREFIX + orderToken));
        System.out.println(flag);
        if(!flag){
            throw new GmallUserException("订单重复提交" , 1021);
        }
        //2、验总价
        List<OrderConfirmItemVO> itemVOS = submitVO.getItems();
        if(CollectionUtils.isEmpty(itemVOS)){
            throw new GmallUserException("商品清单出错" , 1022);
        }
        //页面提交的总价
        BigDecimal totalPrice = submitVO.getTotalPrice();
        //查询数据库中所有的itemVO对应的sku的现在的价格 计算比较
        BigDecimal totalAmount = itemVOS.stream().map(itemVO -> {
            Long skuId = Long.parseLong(itemVO.getSkuId());
            SkuEntity skuEntity = pmsClient.querySkuById(skuId).getData();
            if (skuEntity == null) {
                throw new GmallUserException("商品不存在", 1023);
            }
            //计算单个商品的 总价格
            return skuEntity.getPrice().multiply(new BigDecimal(itemVO.getCount()));
        }).reduce((a, b) -> a.add(b)).get();
        if(totalPrice.compareTo(totalAmount)!=0){
            //价格不一致：
            throw new GmallUserException("页面数据过期,请刷新" , 1023);
        }

        //3、验库存锁库存
        //一个订单可能有多个商品：每个商品都需要验库存锁库存
        //遍历一个个远程访问验库存性能较差： 可以一次性提交集合数据去验库存锁库存
        // wms需要的一个商品锁库存的数据：lock(锁定状态)、orderToken(sku库存锁定时对应的订单)、 skuId、count、wareSkuId(以后释放库存时需要找到对应的库)
        //    一次性有多个商品需要锁库存： 以后有多个用户同时创建订单都锁定库存成功，释放库存时只能释放自己锁定的库存
        //                                  orderToken代表当前用户提交订单对应的唯一的标志
        List<SkuLockVO> skuLockVOS = itemVOS.stream().map(itemVO -> {
            SkuLockVO skuLockVO = new SkuLockVO();
            skuLockVO.setOrderToken(submitVO.getOrderToken());
            skuLockVO.setLock(false);
            skuLockVO.setCount(itemVO.getCount());
            skuLockVO.setSkuId(Long.parseLong(itemVO.getSkuId()));
            return skuLockVO;
        }).collect(Collectors.toList());
        //远程访问wms服务的验库存锁库存接口处理：  返回的skuLockVOs集合中如果每个都锁定成功了代表锁库存成功
        skuLockVOS = wmsClient.checkAndLock(skuLockVOS).getData();
        if(CollectionUtils.isEmpty(skuLockVOS)){
            //验库存锁库存失败：
            throw new GmallUserException("库存不足" , 1024);
        }
        try {
            //程序宕机： 可能被后台杀死   订单创建失败 但是库存锁定了  需要考虑库存的释放
            //出现异常：锁定的库存不会释放
            //4、创建订单保存: 远程访问oms服务
            Long userId = LoginInterceptor.getUserId();
            submitVO.setUserId(userId);
            Long orderId = omsClient.save(submitVO).getData();
            System.out.println("orderId: "+ orderId);
            //5、删除购物车中该订单对应的购物项：
            // 直接远程访问  购物车服务删除购物项接口 影响创建订单接口业务执行性能
            // 如果使用异步 异步代码中远程访问购物车服务删除购物项接口  需要在代码中创建feign客户端远程访问  代码耦合
            // mq: 异步解耦合  发送mq删除购物车购物项消息
            // 查找指定用户的购物车  删除 当前订单的订单项的购物项
            Map<String,String> map = new HashMap<>();
            map.put("userId" , userId.toString());
            map.put("skuIds" , JSON.toJSONString(skuLockVOS.stream()
                    .map(skuLockVO -> skuLockVO.getSkuId().toString()).collect(Collectors.toList())));
            rabbitTemplate.convertAndSend(GmallConsts.MQ_ORDER_EXCHANGE,"cart.del",
                    map);
            return orderToken;
        } catch (Exception e) {

            //出现异常释放库存：使用mq异步解耦合
            //创建订单业务出现异常：释放库存
            //wmsClient.batchReleaseStock(orderToken);
            rabbitTemplate.convertAndSend(GmallConsts.MQ_ORDER_EXCHANGE,"stock.unlock",
                    orderToken);
            throw new GmallUserException("订单创建失败", 1030 );
        }


    }
}
